#include <stdlib.h>

#include <iostream>

#include "scheduler.h"
#ifndef NO_MPI
#include "messenger.h"
#endif

#include "clock.h"
#include "component.h"

using namespace std;

namespace manifold {
namespace kernel {

//====================================================================
//====================================================================
Scheduler :: Scheduler() : m_halted(false), m_simTime(0)
{
    stats_msg_received = 0;
    stats_LBTS_sync = 0;
    #ifndef NO_MPI
    grantedTime = 0;
    #endif
}


//====================================================================
//====================================================================
void Scheduler :: print_stats(ostream& out)
{
    #ifndef NO_MPI
    out << "  LBTS all-gather synchronization: " << stats_LBTS_sync << endl;
    TheMessenger.print_stats(out);
    #endif
}


//####################################################################
// TickedScheduler
//####################################################################

//====================================================================
//====================================================================
void TickedScheduler::Run()
{

#ifndef NO_MPI
  TheMessenger.barrier();
#endif

  while(!m_halted)
    { 
#ifndef NO_MPI
      //check incoming messages
      handle_incoming_messages();
#endif

      // Next we  need to find the clock object with the next earliest tick
      Clock* nextClock = nil;
      Time_t nextClockTime = INFINITY;
      
      Clock::ClockVec_t& clocks = Clock::GetClocks();
      for (size_t i = 0; i < clocks.size(); ++i)
        {
          if (!nextClock)
            { // First one
              nextClock = clocks[i];
              nextClockTime = nextClock->NextTickTime();
            }
          else
            {
              if (clocks[i]->NextTickTime() < nextClockTime)
                {
                  nextClock = clocks[i];
                  nextClockTime = nextClock->NextTickTime();
                }
            }
        }

	assert(nextClock);
	/*
      // If no events found, we are done
      if (nextClock == nil) //?????????????????????????? may never be true
      { 
         m_halted = true;
	 break;
      }   
      */

      double nextTime = nextClockTime; //time of next clock/timed event, whichever is earlier

#ifndef NO_MPI
      if(isSafeToProcess(nextTime))
#endif
        {
              nextClock->ProcessThisTick();
        }//safeToProcess
      // if not safe to process event, go back to beginning of loop to receive events
    }//while(!m_halted)
}


//====================================================================
//====================================================================
void TickedScheduler :: scheduleTimedEvent(EventBase*)
{
    assert(0); //should never happen
}

//====================================================================
//====================================================================
bool TickedScheduler :: cancelTimedEvent(EventId& evid)
{
    assert(0); //should never happen
}

//====================================================================
//====================================================================
EventId TickedScheduler :: peek()
{
    assert(0); //should never happen
}

//====================================================================
//====================================================================
EventBase* TickedScheduler :: GetEarliestEvent()
{
    assert(0); //should never happen
}




//####################################################################
// TimedScheduler
//####################################################################


//====================================================================
//====================================================================
void TimedScheduler::Run()
{

#ifndef NO_MPI
  TheMessenger.barrier();
#endif

  while(!m_halted)
    { 
#ifndef NO_MPI
      //check incoming messages
      handle_incoming_messages();
#endif

      // Get the time of the next timed event
      EventBase* nextEvent = nil;
      if (!m_timedEvents.empty())
      { 
         nextEvent = *m_timedEvents.begin();        
      }
      // If no events found, we are done
      if (nextEvent == nil)
      { 
         m_halted = true;
	 break;
      }   

      double nextTime = nextEvent->time;

#ifndef NO_MPI
      if(isSafeToProcess(nextTime))
#endif
        {
              // Set the simulation time
              m_simTime = nextEvent->time;
              // Call the event handler
              nextEvent->CallHandler();
              // Remove the event from the pending list
              m_timedEvents.erase(m_timedEvents.begin());
              // And delete the event
              delete nextEvent;
        }//safeToProcess
      // if not safe to process event, go back to beginning of loop to receive events
    }//while(!m_halted)
}


//====================================================================
//====================================================================
void TimedScheduler :: scheduleTimedEvent(EventBase* ev)
{
    m_timedEvents.insert(ev);
}


//====================================================================
//====================================================================
bool TimedScheduler :: cancelTimedEvent(EventId& evid)
{
    EventSet_t::iterator it = m_timedEvents.find(&evid);
    if (it == m_timedEvents.end()) return false; // Not found
    m_timedEvents.erase(it);              // Otherwise erase it
    return true;
}

//====================================================================
//====================================================================
EventId TimedScheduler :: peek()
{
    // Return eventid for earliest event, but do not remove it
    // Event list must not be empty
    EventSet_t::iterator it = m_timedEvents.begin();
    return EventId((*it)->time, (*it)->uid);
}

//====================================================================
//====================================================================
EventBase* TimedScheduler :: GetEarliestEvent()
{
    EventSet_t::iterator it = m_timedEvents.begin();
    if (it == m_timedEvents.end()) return 0;
    return *it;
}







//####################################################################
// MixedScheduler
//####################################################################


//====================================================================
//====================================================================
void MixedScheduler::Run()
{

#ifndef NO_MPI
  TheMessenger.barrier();
#endif

  while(!m_halted)
    { 
#ifndef NO_MPI
      //check incoming messages
      handle_incoming_messages();
#endif

      // Get the time of the next timed event
      EventBase* nextEvent = nil;
      if (!m_timedEvents.empty())
      { 
         nextEvent = *m_timedEvents.begin();        
      }
      // Next we  need to find the clock object with the next earliest tick
      Clock* nextClock = nil;
      Time_t nextClockTime = INFINITY;
      
      Clock::ClockVec_t& clocks = Clock::GetClocks();
      for (size_t i = 0; i < clocks.size(); ++i)
        {
          if (!nextClock)
            { // First one
              nextClock = clocks[i];
              nextClockTime = nextClock->NextTickTime();
            }
          else
            {
              if (clocks[i]->NextTickTime() < nextClockTime)
                {
                  nextClock = clocks[i];
                  nextClockTime = nextClock->NextTickTime();
                }
            }
        }
      // If no events found, we are done
      if (nextEvent == nil && nextClock == nil)
      { 
         m_halted = true;
	 break;
      }   


      double nextTime = nextClockTime; //time of next clock/timed event, whichever is earlier
      bool clockIsNext = true;  //the next clock event is earlier
      if(nextEvent != nil && nextEvent->time < nextClockTime)
        {
	  nextTime = nextEvent->time;
	  clockIsNext = false;
	}


#ifndef NO_MPI
      if(isSafeToProcess(nextTime))
#endif
        {
          // Now process either the next event or next clock tick
          // depending on which one is earliest           
          if(clockIsNext)
            { // Process clock event
              nextClock->ProcessThisTick();
            }
          else
            { // Process timed event
              // Set the simulation time
              m_simTime = nextEvent->time;
              // Call the event handler
              nextEvent->CallHandler();
              // Remove the event from the pending list
              m_timedEvents.erase(m_timedEvents.begin());
              // And delete the event
              delete nextEvent;
            }
        }//safeToProcess
      // if not safe to process event, go back to beginning of loop to receive events
    }//while(!m_halted)
}


//====================================================================
//====================================================================
void MixedScheduler :: scheduleTimedEvent(EventBase* ev)
{
    m_timedEvents.insert(ev);
}


//====================================================================
//====================================================================
bool MixedScheduler :: cancelTimedEvent(EventId& evid)
{
    EventSet_t::iterator it = m_timedEvents.find(&evid);
    if (it == m_timedEvents.end()) return false; // Not found
    m_timedEvents.erase(it);              // Otherwise erase it
    return true;
}

//====================================================================
//====================================================================
EventId MixedScheduler :: peek()
{
    // Return eventid for earliest event, but do not remove it
    // Event list must not be empty
    EventSet_t::iterator it = m_timedEvents.begin();
    return EventId((*it)->time, (*it)->uid);
}


//====================================================================
//====================================================================
EventBase* MixedScheduler :: GetEarliestEvent()
{
    EventSet_t::iterator it = m_timedEvents.begin();
    if (it == m_timedEvents.end()) return 0;
    return *it;
}





#ifndef NO_MPI
//if parallel simulation
//====================================================================
//====================================================================
void Scheduler :: handle_incoming_messages()
{
    int received = 0;
    do {
        Message_s& msg = TheMessenger.irecv_message(&received);
	if(received != 0) {
	    Component* comp = Component :: GetComponent<Component>(msg.compIndex);
	    switch(msg.type) {
	        case Message_s :: M_UINT32:
		    comp->Recv_remote(msg.inputIndex, msg.sendTick, msg.recvTick,
		                      msg.sendTime, msg.recvTime, msg.uint32_data);
		    break;
	        case Message_s :: M_UINT64:
		    comp->Recv_remote(msg.inputIndex, msg.sendTick, msg.recvTick,
		                      msg.sendTime, msg.recvTime, msg.uint64_data);
		    break;
	        case Message_s :: M_SERIAL:
		    comp->Recv_remote(msg.inputIndex, msg.sendTick, msg.recvTick,
		                      msg.sendTime, msg.recvTime, msg.data, msg.data_len);
		    break;
		default:
		    std::cerr << "unknown message type" << std::endl;
		    exit(1);
	    }//switch
	    #ifdef STATS
	    stats_msg_received++;
	    #endif
	}
    }while(received != 0);
}
#endif //#ifndef NO_MPI


#ifndef NO_MPI
//if parallel simulation
//====================================================================
//====================================================================
bool Scheduler::isSafeToProcess(double requestTime)
{
    static LBTS_Msg* LBTS = new LBTS_Msg[TheMessenger.get_node_size()];

      if(requestTime <= grantedTime)
        {
	  return true;
        }
      else
        {
	  LBTS_Msg lbts_msg = {0,0,0,0,0};
	  lbts_msg.tx_count = TheMessenger.get_numSent();
	  lbts_msg.rx_count = TheMessenger.get_numReceived();
	  lbts_msg.smallest_time = requestTime;
	  int nodeId = TheMessenger.get_node_id();
	  LBTS[nodeId] = lbts_msg;

	  TheMessenger.allGather((char*)&(LBTS[nodeId]), sizeof(LBTS_Msg), (char*)LBTS);

	  #ifdef STATS
	  stats_LBTS_sync++;
	  #endif
	  //MPI_Allgather(&(LBTS[nodeId]), sizeof(LBTS_MSG), MPI_BYTE, LBTS, 
	//		sizeof(LBTS_MSG), MPI_BYTE, MPI_COMM_WORLD);
	  int rx=0;
	  int tx=0;
	  double smallest_time = LBTS[0].smallest_time;

	  for(int i=0; i<TheMessenger.get_node_size(); i++)
	    {
	      tx+=LBTS[i].tx_count;
	      rx+=LBTS[i].rx_count;

	      if(LBTS[i].smallest_time < smallest_time)
		{
		  smallest_time=LBTS[i].smallest_time;
		}
	    }
	  if(rx==tx)
	    {
	      //grantedTime=DistributedSimulator::smallest_time+LinkRTI::minTxTime;
	      grantedTime = smallest_time;
	      if(requestTime <= grantedTime)
		return true;
              else
	        return false;
	    }
	  else
	    {
	      return false;
	    }
	}
}
#endif  //#ifndef NO_MPI



} //kernel
} //manifold


